#ifndef __RED_OBJECT_H__
#define __RED_OBJECT_H__

#include <stdint.h>
#include <string.h>

/* shared library export declarations */
#ifndef RED_EXPORT
#	ifndef RED_LIB_EXPORT
#		define RED_EXPORT
#	else
#		define RED_EXPORT		__attribute__ ((visibility ("default")))
#	endif
#endif

typedef struct _RedObject		RedObject;
typedef struct _RedTypeObject	RedTypeObject;

#define RED_OBJECT_HASHED			0x00000001
#define RED_OBJECT_STATIC			0x00000002
#define RED_OBJECT_IN_REPR			0x00000004

typedef struct _RedObject
{
	int				 ob_ref;
	size_t			 ob_size;
	uint32_t		 ob_hash;
	uint32_t		 ob_flags;
	RedTypeObject	*ob_type;
	RedObject		*ob_prev;
	RedObject		*ob_next;
	RedObject		*ob_attrs;
} RedObject;

#define RedObject_HEAD			RedObject ob_head;
#define RedObject_HEAD_INIT(type)			\
	.ob_head =								\
	{										\
		.ob_ref = 1,						\
		.ob_hash = 0,						\
		.ob_size = 0,						\
		.ob_next = NULL,					\
		.ob_prev = NULL,					\
		.ob_type = &(type),					\
		.ob_attrs = NULL,					\
		.ob_flags = RED_OBJECT_STATIC,		\
	},

typedef uint32_t (* tp_hash_fn_t)(RedObject *self);
typedef RedObject *(* tp_alloc_fn_t)(RedTypeObject *self, RedObject *args);

typedef char (* tp_init_fn_t)(RedObject *self, RedObject *args);
typedef void (* tp_release_fn_t)(RedObject *self);

typedef void (* tp_clear_fn_t)(RedObject *self);
typedef char (* tp_visit_fn_t)(RedObject *self, void *args);
typedef void (* tp_traverse_fn_t)(RedObject *self, tp_visit_fn_t visit, void *args);

typedef RedObject *(* tp_delter_fn_t)(RedObject *self, RedObject *key);
typedef RedObject *(* tp_getter_fn_t)(RedObject *self, RedObject *key);
typedef RedObject *(* tp_setter_fn_t)(RedObject *self, RedObject *key, RedObject *value);

typedef RedObject *(* tp_unary_fn_t)(RedObject *self);
typedef RedObject *(* tp_binary_fn_t)(RedObject *self, RedObject *args);
typedef RedObject *(* tp_ternary_fn_t)(RedObject *self, RedObject *other, RedObject *args);
typedef RedObject *(* tp_nullary_fn_t)(void);

#define RED_TYPE_INITED			0x00000001
#define RED_TYPE_PREPARED		0x00000002
#define RED_TYPE_ATTR_MUTABLE	0x00000004

typedef struct _RedMethodDesc
{
	const char		*name;
	tp_binary_fn_t	 method;
} RedMethodDesc;

typedef struct _RedTypeObject
{
	RedObject_HEAD

	/* type basic */
	size_t				 tp_size;
	const char			*tp_name;
	uint32_t			 tp_flags;
	RedTypeObject		*tp_parent;

	/* object basic methods */
	tp_hash_fn_t		 tp_hash;
	tp_init_fn_t		 tp_init;
	tp_alloc_fn_t		 tp_alloc;
	tp_release_fn_t		 tp_release;

	/* GC specific methods */
	tp_clear_fn_t		 tp_clear;
	tp_traverse_fn_t	 tp_traverse;

	/* getters and setters */
	tp_delter_fn_t		 tp_delattr;
	tp_getter_fn_t		 tp_getattr;
	tp_setter_fn_t		 tp_setattr;

	tp_delter_fn_t		 tp_delitem;
	tp_getter_fn_t		 tp_getitem;
	tp_setter_fn_t		 tp_setitem;

	/* inspection methods */
	tp_unary_fn_t		 tp_dir;
	tp_unary_fn_t		 tp_len;
	tp_unary_fn_t		 tp_str;
	tp_unary_fn_t		 tp_repr;
	tp_binary_fn_t		 tp_invoke;

	/* numeric methods */
	tp_binary_fn_t		 tp_add;
	tp_binary_fn_t		 tp_sub;
	tp_binary_fn_t		 tp_mul;
	tp_binary_fn_t		 tp_div;
	tp_binary_fn_t		 tp_mod;
	tp_binary_fn_t		 tp_pow;
	tp_binary_fn_t		 tp_bit_or;
	tp_binary_fn_t		 tp_bit_and;
	tp_binary_fn_t		 tp_bit_xor;
	tp_unary_fn_t		 tp_bit_not;
	tp_binary_fn_t		 tp_lshift;
	tp_binary_fn_t		 tp_rshift;

	tp_binary_fn_t		 tp_aug_add;
	tp_binary_fn_t		 tp_aug_sub;
	tp_binary_fn_t		 tp_aug_mul;
	tp_binary_fn_t		 tp_aug_div;
	tp_binary_fn_t		 tp_aug_mod;
	tp_binary_fn_t		 tp_aug_pow;
	tp_binary_fn_t		 tp_aug_or;
	tp_binary_fn_t		 tp_aug_and;
	tp_binary_fn_t		 tp_aug_xor;
	tp_binary_fn_t		 tp_aug_lshift;
	tp_binary_fn_t		 tp_aug_rshift;

	/* boolean methods */
	tp_binary_fn_t		 tp_bool_or;
	tp_binary_fn_t		 tp_bool_and;
	tp_binary_fn_t		 tp_bool_xor;
	tp_unary_fn_t		 tp_bool_not;

	/* comparison methods */
	tp_binary_fn_t		 tp_eq;
	tp_binary_fn_t		 tp_le;
	tp_binary_fn_t		 tp_ge;
	tp_binary_fn_t		 tp_neq;
	tp_binary_fn_t		 tp_leq;
	tp_binary_fn_t		 tp_geq;
	tp_binary_fn_t		 tp_compare;

	/* positive and negative methods */
	tp_unary_fn_t		 tp_pos;
	tp_unary_fn_t		 tp_neg;

	/* custom methods */
	RedMethodDesc		*tp_methods;
} RedTypeObject;

#define RedType_INIT(name, class, parent)			\
	RedObject_HEAD_INIT(RedType_Type)				\
	.tp_name = (name),								\
	.tp_size = sizeof(class),						\
	.tp_flags = 0,									\
	.tp_parent = &(parent),

#define RedType_Mutable_INIT(name, class, parent)	\
	RedObject_HEAD_INIT(RedType_Type)				\
	.tp_name = (name),								\
	.tp_size = sizeof(class),						\
	.tp_flags = RED_TYPE_ATTR_MUTABLE,				\
	.tp_parent = &(parent),

/* instance macros */

#define ATOMIC_INC(v)		__sync_add_and_fetch(&(v), 1)
#define ATOMIC_DEC(v)		__sync_sub_and_fetch(&(v), 1)

#define Red_REF(o)			((o) ? ATOMIC_INC(((RedObject *)(o))->ob_ref) : 0)
#define Red_UNREF(o)									\
	do													\
	{													\
		RedObject *_o = (RedObject *)(o);				\
		(o) = NULL;										\
		if (_o && ATOMIC_DEC(_o->ob_ref) == 0)			\
			_o->ob_type->tp_release(_o);				\
	} while (0)

#define Red_VISIT(o)									\
	do													\
	{													\
		if ((o) && !visit(((RedObject *)(o)), args))	\
			return;										\
	} while (0)

#define Red_INIT()													\
	({																\
		char _r = 1;												\
		RedTypeObject *_prev = NULL;								\
		RedTypeObject *_type = self->ob_type;						\
		while (_prev != _type)										\
		{															\
			if (_type->tp_init != _type->tp_parent->tp_init)		\
			{														\
				_r = _type->tp_parent->tp_init(self, args);			\
				break;												\
			}														\
			_prev = _type;											\
			_type = _type->tp_parent;								\
		}															\
		_r;															\
	})

#define Red_RELEASE()												\
	do																\
	{																\
		RedTypeObject *_prev = NULL;								\
		RedTypeObject *_type = self->ob_type;						\
		while (_prev != _type)										\
		{															\
			if (_type->tp_release != _type->tp_parent->tp_release)	\
			{														\
				_type->tp_parent->tp_release(self);					\
				break;												\
			}														\
			_prev = _type;											\
			_type = _type->tp_parent;								\
		}															\
	} while (0)

typedef void (* RedExitFunction)(void *args);

/* object manager */

RED_EXPORT extern int Red_Argc;
RED_EXPORT extern char **Red_Argv;

RED_EXPORT char Red_Init(int argc, char **argv);
RED_EXPORT void Red_Cleanup(void);
RED_EXPORT void Red_ExitFunction(RedExitFunction fn, void *args);

/* type protocols */

RED_EXPORT extern RedTypeObject RedType_Type;
RED_EXPORT extern RedTypeObject RedType_Object;

#define RedType_Exact(o)		((o)->ob_type == &RedType_Type)
#define RedObject_Exact(o)		((o)->ob_type == &RedType_Object)

RED_EXPORT void RedType_Init(RedTypeObject *type);
RED_EXPORT char RedType_Prepare(RedTypeObject *type);
RED_EXPORT void RedType_Cleanup(RedTypeObject *type);
RED_EXPORT char RedType_IsInstance(RedObject *self, RedTypeObject *type);
RED_EXPORT char RedType_IsSubclass(RedTypeObject *self, RedTypeObject *type);

RED_EXPORT uint32_t RedObject_Hash(RedObject *self);
RED_EXPORT const char *RedObject_ClassName(RedObject *self); /* return value of this function must be used IMMEDIATELY !!! */

RED_EXPORT RedObject *RedObject_Create(RedTypeObject *type, RedObject *args);
RED_EXPORT RedObject *RedObject_CreateVar(RedTypeObject *type, RedObject *args, size_t space);

/* object protocols */

RED_EXPORT char RedObject_Print(RedObject *self);
RED_EXPORT char RedObject_Callable(RedObject *self);
RED_EXPORT char RedObject_ReprEnter(RedObject *self);
RED_EXPORT void RedObject_ReprLeave(RedObject *self);

RED_EXPORT RedObject *RedObject_DelAttr(RedObject *self, RedObject *attr);
RED_EXPORT RedObject *RedObject_GetAttr(RedObject *self, RedObject *attr);
RED_EXPORT RedObject *RedObject_SetAttr(RedObject *self, RedObject *attr, RedObject *value);

RED_EXPORT RedObject *RedObject_DelItem(RedObject *self, RedObject *item);
RED_EXPORT RedObject *RedObject_GetItem(RedObject *self, RedObject *item);
RED_EXPORT RedObject *RedObject_SetItem(RedObject *self, RedObject *item, RedObject *value);

RED_EXPORT RedObject *RedObject_Dir(RedObject *self);
RED_EXPORT RedObject *RedObject_Len(RedObject *self);
RED_EXPORT RedObject *RedObject_Str(RedObject *self);
RED_EXPORT RedObject *RedObject_Repr(RedObject *self);

RED_EXPORT RedObject *RedObject_Eq(RedObject *self, RedObject *other);
RED_EXPORT RedObject *RedObject_Geq(RedObject *self, RedObject *other);
RED_EXPORT RedObject *RedObject_Leq(RedObject *self, RedObject *other);
RED_EXPORT RedObject *RedObject_Neq(RedObject *self, RedObject *other);
RED_EXPORT RedObject *RedObject_Compare(RedObject *self, RedObject *other);

RED_EXPORT RedObject *RedObject_Invoke(RedObject *self, RedObject *args);
RED_EXPORT RedObject *RedObject_InvokeMethod(RedObject *self, const char *method, RedObject *args);
RED_EXPORT RedObject *RedObject_InvokeMethodVa(RedObject *self, const char *method, int count, ...);

/* convience functions for manual object initialization */
/* these functions don't check argument types, you must ensure */

RED_EXPORT char RedObject_AddConst(RedObject *self, const char *name, RedObject *value);	/* this function does NOT unref value */
RED_EXPORT char RedObject_AddObject(RedObject *self, const char *name, RedObject *value);	/* but this function does */

RED_EXPORT char RedObject_AddVarFunc(RedObject *self, const char *name, tp_unary_fn_t func);
RED_EXPORT char RedObject_AddVarMethod(RedObject *self, const char *name, tp_binary_fn_t method);

RED_EXPORT char RedObject_AddUnary(RedObject *self, const char *name, tp_unary_fn_t unary);
RED_EXPORT char RedObject_AddBinary(RedObject *self, const char *name, tp_binary_fn_t binary);
RED_EXPORT char RedObject_AddTernary(RedObject *self, const char *name, tp_ternary_fn_t ternary);
RED_EXPORT char RedObject_AddNullary(RedObject *self, const char *name, tp_nullary_fn_t nullary);

#endif /* __RED_OBJECT_H__ */
